﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using FifaChamps.API.Logic;
using GemBox.Spreadsheet;
using System.Security.Cryptography;
using System.IO;

namespace Runner
{
    class Program
    {
        public static void Main(string[] args)
        {
            Console.Write("How much groups would you like: "); var num_groups = Convert.ToInt32(Console.ReadLine());
            Console.Write("How much players per group: "); var num_players_per_group = Convert.ToInt32(Console.ReadLine());
            Console.Write("Where would you like to save the file: "); string path = Console.ReadLine();

            var tourny = FifaChamps.Tools.FixtureBuilder.TournamentManager.BuildTournament(num_groups, num_players_per_group);
            var stream = FifaChamps.Tools.FixtureBuilder.TournamentManager.ExportTournament(tourny);
            stream.Seek(0, SeekOrigin.Begin);

            byte[] buffer = new byte[stream.Length];
            FileStream file_stream = new FileStream(path, FileMode.CreateNew, FileAccess.Write);
            using (file_stream)
            {
                stream.Read(buffer, 0, buffer.Length);
                file_stream.Write(buffer, 0, buffer.Length);
                file_stream.Flush();
            }

        }

        static void Main2(string[] args)
        {
            Console.Write("How much groups would you like: "); var num_groups = Convert.ToInt32(Console.ReadLine());
            Console.Write("How much players per group: "); var num_players_per_group = Convert.ToInt32(Console.ReadLine());            
            int playerID = 1;

            xl_Tournament tournament = new xl_Tournament();

            #region Build Tournament Data
            for (int i = 0; i < num_groups; i++)
            {
                xl_Group m_group = new xl_Group();
                m_group.groupname = (i + 1).ToString();
                m_group.players = new List<xl_Player>();
                m_group.fixtures = new List<xl_Fixture>();
                for (int j = 0; j < num_players_per_group; j++)
                {
                    m_group.players.Add(new xl_Player() { id = playerID, name = "" });
                    playerID++;
                }

                foreach (var player in m_group.players.OrderBy(x => x.id))
                {
                    int cp = player.id;

                    foreach (var cplayer in m_group.players.Where(x => x.id > cp))
                    {
                        m_group.fixtures.Add(new xl_Fixture() { homeplayer = cp, awayplayer = cplayer.id });
                    }
                }
                
                //m_group.fixtures.Shuffle();
                m_group.fixtures = Shuffle(m_group.fixtures);
                tournament.groups.Add(m_group);
                Console.WriteLine("Group: " + m_group.groupname);
                m_group.fixtures.ForEach(x => Console.WriteLine("home: " + x.homeplayer.ToString() + " away: " + x.awayplayer.ToString()));

                Console.WriteLine("------");
            }
            #endregion            
            BuildSheet(tournament);
        }

        static void Main1(string[] args)
        {
            FifaChamps.API.Logic.Tournament t = Tournament.Factory("shams test 3", "shams place", DateTime.Now, default(DateTime?));
            int tournamentID = t.Add();

            FifaChamps.API.Logic.Stage stage = Stage.Factory(tournamentID, "Group of 32 v1", DateTime.Now, default(DateTime?), FifaChamps.API.Logic.StageType.Group);
            int stageID = stage.Add();

            var players = Player.Get_Players();

            var player_id_list = players.Take(32).Select(play => play.ID).ToList();
            player_id_list.ForEach(x => Console.WriteLine(x));

            player_id_list.ForEach(play => stage.Add_StagePlayer(play));

            int phaseID = stage.Add_Phase(PhaseParam.Factory("name", "Group 1"));            
            var phase1 = Phase.Get_One(phaseID, StageType.Group);

            stage.Get_StagePlayers().Take(4).ToList().ForEach(player => phase1.Add_PhasePlayer(player.ID));

            var pp = phase1.Get_PhasePlayers().Take(2);

            Fixture fixture = Fixture.Add_Fixture(stage.StageType, phase1.ID, pp.First().Value.Name + " vs " +
                pp.Skip(1).First().Value.Name, pp.First().Value.PhasePlayerID, pp.Skip(1).First().Value.PhasePlayerID);

            fixture.Set_HomeResult(EndResult.Win, 3, 1);
            fixture.Set_AwayResult(EndResult.Loss, 1, 3);

            fixture.Save();
        }

        public static void BuildSheet(xl_Tournament tournament)
        {
            int current_row = 0;
            int current_fixture_row = 0;
            Dictionary<int, int> row_player = new Dictionary<int, int>();

            ExcelFile file = new ExcelFile();            
            ExcelWorksheet group_sheet = file.Worksheets.Add("Group Stages");

            //make the entire sheet background white                    
            SetupWorkArea(group_sheet);

            foreach (var group in tournament.groups)
            {
                //build header row
                SetupGroupTable(group_sheet, current_row, "Group " + group.groupname);
                ++current_row;

                //build fixture header
                SetupGroupFixtureTable(group_sheet, current_fixture_row, "Group " + group.groupname);
                ++current_fixture_row;

                //build player rows
                foreach (var player in group.players)
                {
                    SetupPlayerGroupRow(group_sheet, row_player, player, current_row);
                    ++current_row;
                }

                //build fixture rows
                foreach (var fixture in group.fixtures)
                {
                    SetupPlayerFixtureRow(group_sheet, fixture, row_player, current_fixture_row);
                    //points
                    ++current_fixture_row;
                }


                if (current_fixture_row > current_row) current_row = current_fixture_row;
                else if (current_row > current_fixture_row) current_fixture_row = current_row;
            }

            file.SaveXls(@"C:\Drop\Group_Stages_" + DateTime.Today.ToString("yyyy-MM-dd") + ".xls");
        }

        private static void SetupPlayerFixtureRow(ExcelWorksheet group_sheet, xl_Fixture fixture, Dictionary<int, int> row_player, int current_fixture_row)
        {
            int homeIndex = row_player[fixture.homeplayer];
            int homeFormulaIndex = homeIndex + 1;
            int awayIndex = row_player[fixture.awayplayer];
            int awayFormulaIndex = awayIndex + 1;
            int fixtureFormulaIndex_Row = current_fixture_row + 1;
            var fixture_range = group_sheet.Cells.GetSubrangeAbsolute(current_fixture_row, 11, current_fixture_row, 16);
            fixture_range.Merged = true;
            fixture_range.Style.Borders.SetBorders(MultipleBorders.Horizontal, Color.LightGray, LineStyle.Thin);
            fixture_range.Style.Borders.SetBorders(MultipleBorders.Vertical, Color.LightGray, LineStyle.Thin);
            fixture_range.Merged = false;
            group_sheet.Cells[current_fixture_row, 11].Formula = string.Format("=IF(A{0}=\"\",\"\",A{0})", (homeFormulaIndex).ToString());
            group_sheet.Columns[11].Width = 50 * 150;
            group_sheet.Cells[current_fixture_row, 13].Value = "v";
            group_sheet.Cells[current_fixture_row, 15].Formula = string.Format("=IF(A{0}=\"\",\"\",A{0})", (awayFormulaIndex).ToString());
            group_sheet.Columns[15].Width = 50 * 150;
            group_sheet.Cells[current_fixture_row, 16].Value = false;

            //update home formula

            //update played
            string formula = group_sheet.Cells[homeIndex, 1].Formula;
            group_sheet.Cells[homeIndex, 1].Formula = (string.IsNullOrEmpty(formula)) ? string.Format("=IF(Q{0},+1,+0)", fixtureFormulaIndex_Row) : formula + string.Format("+IF(Q{0},+1,+0)", fixtureFormulaIndex_Row);
            formula = group_sheet.Cells[awayIndex, 1].Formula;
            group_sheet.Cells[awayIndex, 1].Formula = (string.IsNullOrEmpty(formula)) ? string.Format("=IF(Q{0},+1,+0)", fixtureFormulaIndex_Row) : formula + string.Format("+IF(Q{0},+1,+0)", fixtureFormulaIndex_Row);

            //update win formula
            formula = group_sheet.Cells[homeIndex, 2].Formula;
            group_sheet.Cells[homeIndex, 2].Formula = (string.IsNullOrEmpty(formula)) ? string.Format("=IF(M{0}>O{0},+1,+0)", fixtureFormulaIndex_Row) : formula + string.Format("+IF(M{0}>O{0},+1,+0)", fixtureFormulaIndex_Row);
            formula = group_sheet.Cells[awayIndex, 2].Formula;
            group_sheet.Cells[awayIndex, 2].Formula = (string.IsNullOrEmpty(formula)) ? string.Format("=IF(M{0}<O{0},+1,+0)", fixtureFormulaIndex_Row) : formula + string.Format("+IF(M{0}<O{0},+1,+0)", fixtureFormulaIndex_Row);

            //update draw formula
            formula = group_sheet.Cells[homeIndex, 3].Formula;
            group_sheet.Cells[homeIndex, 3].Formula = (string.IsNullOrEmpty(formula)) ? string.Format("=IF(M{0}=O{0},IF(Q{0},+1,+0),+0)", fixtureFormulaIndex_Row) : formula + string.Format("+IF(M{0}=O{0},IF(Q{0},+1,+0),+0)", fixtureFormulaIndex_Row);
            formula = group_sheet.Cells[awayIndex, 3].Formula;
            group_sheet.Cells[awayIndex, 3].Formula = (string.IsNullOrEmpty(formula)) ? string.Format("=IF(M{0}=O{0},IF(Q{0},+1,+0),+0)", fixtureFormulaIndex_Row) : formula + string.Format("+IF(M{0}=O{0},IF(Q{0},+1,+0),+0)", fixtureFormulaIndex_Row);

            //update draw formula
            formula = group_sheet.Cells[homeIndex, 4].Formula;
            group_sheet.Cells[homeIndex, 4].Formula = (string.IsNullOrEmpty(formula)) ? string.Format("=IF(M{0}<O{0},+1,+0)", fixtureFormulaIndex_Row) : formula + string.Format("+IF(M{0}<O{0},+1,+0)", fixtureFormulaIndex_Row);
            formula = group_sheet.Cells[awayIndex, 4].Formula;
            group_sheet.Cells[awayIndex, 4].Formula = (string.IsNullOrEmpty(formula)) ? string.Format("=IF(M{0}>O{0},+1,+0)", fixtureFormulaIndex_Row) : formula + string.Format("+IF(M{0}>O{0},+1,+0)", fixtureFormulaIndex_Row);

            //update goals for
            formula = group_sheet.Cells[homeIndex, 5].Formula;
            group_sheet.Cells[homeIndex, 5].Formula = (string.IsNullOrEmpty(formula)) ? string.Format("=M{0}", fixtureFormulaIndex_Row) : formula + string.Format("+M{0}", fixtureFormulaIndex_Row);
            formula = group_sheet.Cells[awayIndex, 5].Formula;
            group_sheet.Cells[awayIndex, 5].Formula = (string.IsNullOrEmpty(formula)) ? string.Format("=O{0}", fixtureFormulaIndex_Row) : formula + string.Format("+O{0}", fixtureFormulaIndex_Row);

            //update goals against
            formula = group_sheet.Cells[homeIndex, 6].Formula;
            group_sheet.Cells[homeIndex, 6].Formula = (string.IsNullOrEmpty(formula)) ? string.Format("=O{0}", fixtureFormulaIndex_Row) : formula + string.Format("+O{0}", fixtureFormulaIndex_Row);
            formula = group_sheet.Cells[awayIndex, 6].Formula;
            group_sheet.Cells[awayIndex, 6].Formula = (string.IsNullOrEmpty(formula)) ? string.Format("=M{0}", fixtureFormulaIndex_Row) : formula + string.Format("+M{0}", fixtureFormulaIndex_Row);
        }

        private static void SetupPlayerGroupRow(ExcelWorksheet group_sheet, Dictionary<int, int> row_player, xl_Player player, int current_row)
        {
            row_player.Add(player.id, current_row);
            var player_row_range = group_sheet.Cells.GetSubrangeAbsolute(current_row, 0, current_row, 8);
            player_row_range.Merged = true;
            player_row_range.Style.Borders.SetBorders(MultipleBorders.Horizontal, Color.LightGray, LineStyle.Thin);
            player_row_range.Style.Borders.SetBorders(MultipleBorders.Vertical, Color.LightGray, LineStyle.Thin);
            player_row_range.Merged = false;
            group_sheet.Cells[current_row, 0].Value = player.name;
            group_sheet.Cells[current_row, 1].Value = 0;
            group_sheet.Cells[current_row, 2].Value = 0;
            group_sheet.Cells[current_row, 3].Value = 0;
            group_sheet.Cells[current_row, 4].Value = 0;
            group_sheet.Cells[current_row, 5].Value = 0;
            group_sheet.Cells[current_row, 6].Value = 0;
            group_sheet.Cells[current_row, 7].Value = 0;
            group_sheet.Cells[current_row, 7].Formula = string.Format("=F{0}-G{0}", current_row + 1);
            group_sheet.Cells[current_row, 8].Value = 0;
            group_sheet.Cells[current_row, 8].Formula = string.Format("=(C{0}*3)+(D{0}*1)+(E{0}*0)", current_row + 1);
        }

        private static void SetupGroupFixtureTable(ExcelWorksheet group_sheet, int current_fixture_row, string group_name)
        {
            CellRange fixture_header_range = group_sheet.Cells.GetSubrangeAbsolute(current_fixture_row, 11, current_fixture_row, 16);
            fixture_header_range.Merged = true;
            fixture_header_range.Style.FillPattern.SetPattern(FillPatternStyle.Gray75, System.Drawing.Color.LightGray, System.Drawing.Color.LightGray);
            fixture_header_range.Style.HorizontalAlignment = HorizontalAlignmentStyle.Center;
            fixture_header_range.Style.Borders.SetBorders(MultipleBorders.Horizontal, Color.LightGray, LineStyle.Thin);
            fixture_header_range.Style.Borders.SetBorders(MultipleBorders.Vertical, Color.LightGray, LineStyle.Thin);
            group_sheet.Cells[current_fixture_row, 11].Value = group_name + " Fixtures";
        }
      
        private static void SetupWorkArea(ExcelWorksheet sheet)
        {
            CellRange worksheet = sheet.Cells.GetSubrangeAbsolute(0, 0, 151, 20);
            worksheet.Merged = true;
            worksheet.Style.Font.Name = "Calibri";
            worksheet.Style.Font.Size = 300;
            worksheet.Style.Borders.SetBorders(MultipleBorders.Horizontal, System.Drawing.Color.White, LineStyle.Thin);
            worksheet.Style.Borders.SetBorders(MultipleBorders.Vertical, System.Drawing.Color.White, LineStyle.Thin);
            worksheet.Merged = false;
        }

        private static void SetupGroupTable(ExcelWorksheet group_sheet, int current_row, string group_name)
        {
            group_sheet.Columns[0].Width = 50 * 256;
            CellRange groupHeader = group_sheet.Cells.GetSubrangeAbsolute(current_row, 0, current_row, 8);
            groupHeader.Merged = true;
            groupHeader.Style.Borders.SetBorders(MultipleBorders.Horizontal, System.Drawing.Color.LightGray, LineStyle.Thin);
            groupHeader.Style.Borders.SetBorders(MultipleBorders.Vertical, System.Drawing.Color.LightGray, LineStyle.Thin);
            groupHeader.Style.FillPattern.SetPattern(FillPatternStyle.Gray75, System.Drawing.Color.LightGray, System.Drawing.Color.LightGray);
            groupHeader.Merged = false;
            group_sheet.Cells[current_row, 0].Value = group_name;
            group_sheet.Cells[current_row, 1].Value = "P";
            group_sheet.Cells[current_row, 2].Value = "W";
            group_sheet.Cells[current_row, 3].Value = "D";
            group_sheet.Cells[current_row, 4].Value = "L";
            group_sheet.Cells[current_row, 5].Value = "GF";
            group_sheet.Cells[current_row, 6].Value = "GA";
            group_sheet.Cells[current_row, 7].Value = "GD";
            group_sheet.Cells[current_row, 8].Value = "PTS";
        }

        private static List<xl_Fixture> Shuffle(List<xl_Fixture> fixtures)
        {
            //build a 2d array with unique values
            List<Pair<int>> store = new List<Pair<int>>();
            List<Pair<xl_Fixture>> complete = new List<Pair<xl_Fixture>>();
            List<xl_Fixture> neworder = new List<xl_Fixture>();
            var home = fixtures.Select(x => x.homeplayer).ToList();
            var away = fixtures.Select(x => x.awayplayer).ToList();
            var merged = home.Union(away).OrderBy(x => x).Distinct().ToList();            

            #region init store with 0
            foreach (var item in merged)
            {
                store.Add(new Pair<int>(item, 0));
            }
            #endregion
            #region init fixtures
            foreach (var item in fixtures)
            {
                complete.Add(new Pair<xl_Fixture>(item, 0));
            }
            #endregion

            //loop through as much times of fixtures as there is
            for (int i = 0; i < fixtures.Count; i++)
            {
                bool found = false;
                List<int> triedList = new List<int>();
                
                var p1 = store.OrderBy(x => x.count).First();

                var results = (from fix in complete
                            join play1 in store
                            on fix.id.homeplayer equals play1.id
                            join play2 in store
                            on fix.id.awayplayer equals play2.id
                            where fix.count == 0 && (fix.id.homeplayer == p1.id || fix.id.awayplayer == p1.id)
                            select new { fixture = fix, hp = play1, ap = play2 }).ToList();

                Pair<xl_Fixture> fixture = null;

                if (results.Where(x => x.hp.id == p1.id).Any())
                {
                    fixture = results.Where(x => x.hp.id == p1.id).OrderBy(x => x.ap.count).First().fixture;
                }
                else
                {
                    fixture = results.Where(x => x.ap.id == p1.id).OrderBy(x => x.hp.count).First().fixture;
                }

                if (fixture != null)
                {
                    fixture.count++;
                    neworder.Add(fixture.id);
                    store.Where(x => x.id == fixture.id.homeplayer).First().count++;
                    store.Where(x => x.id == fixture.id.awayplayer).First().count++;
                    found = true;
                }
                else
                {
                    throw new Exception("Error");
                }
            }

            return neworder;
        }

        public class Pair<T>
        {
            public T id;
            public int count;

            public Pair(T i, int c)
            {
                id = i;
                count = c;
            }
        }

        public class xl_Player
        {
            public int id;
            public string name;
        }

        public class xl_Group
        {
            public string groupname;
            public List<xl_Player> players;
            public List<xl_Fixture> fixtures;
        }

        public class xl_Tournament
        {
            public List<xl_Group> groups;

            public xl_Tournament()
            {
                groups = new List<xl_Group>();
            }
        }

        public class xl_Fixture
        {
            public int homeplayer;
            public int awayplayer;
        }

        public class xl_RowIndex
        {
            public int rowid;
            public int playerid;
        }
    }

    public static class Utils
    {
        public static void Shuffle<T>(this IList<T> list)
        {
            RNGCryptoServiceProvider provider = new RNGCryptoServiceProvider();
            int n = list.Count;
            while (n > 1)
            {
                byte[] box = new byte[1];
                do provider.GetBytes(box);
                while (!(box[0] < n * (Byte.MaxValue / n)));
                int k = (box[0] % n);
                n--;
                T value = list[k];
                list[k] = list[n];
                list[n] = value;
            }
        }
    }
}
